package ogmios.components.munches;

import javax.swing.*;
import java.util.*;
import ogmios.core.*;

/**
 * The interpreter and executor of Macros. 
 */
public class MacroExecutor  implements Chainable, ChainableListener
{
  public static final String IF_SYMBOL = "#if";
  public static final String ELSE_SYMBOL = "#else";
  public static final String END_IF_SYMBOL = "#endif";
  public static final int SUCCESSFUL = 0;
  public static final int UNSUCCESSFUL = 1;
  public static final int INCOMPLETE  = 2;
  private volatile int i;
  private volatile int elseIndex;
  private volatile int end;
  private volatile boolean conditionalCheck = false;
  private Context shell;
  private Command command;
  private List<String> args;
  private ChainableListener[] listeners;

  /**
  * Executes the given script.
  * @param     shell     The context to operate with
  * @param     args     The script broken down into lines
  * @param     listeners     ChainableListeners to be alerted when the script is complete.
  */
  public void execute(Context shell, List<String> args, ChainableListener... listeners) throws Throwable
  {
    this.shell = shell;
    this.args = args instanceof RandomAccess ? args : new ArrayList<String>(args);
    this.listeners = listeners;
    execute();
  }
 /**
 * Runs a single line and returns either SUCCESSFUL, UNSUCCESSFUL, or INCOMPLETE if the command is a Chainable.<br>
 * Under most circumstances, this should not be invoked manually.
 */ 
 public int ranSuccessfully(String commandLine)
 {
   if (commandLine.length() == 0)
   {
     return SUCCESSFUL;
   }
     final List<String> args = CommandManager.splitCommandLine(commandLine);
     if (args != null && !args.isEmpty())
     {
       command = CommandManager.get(args.remove(0));
       if (command != null)
       {
        try
        {
          if (command instanceof Chainable)
          {
             ((Chainable)command).execute(shell, args, MacroExecutor.this);
           return INCOMPLETE;
          }
          else
          {
             command.execute(shell, args);
          }
           return SUCCESSFUL;
       }
       catch(Throwable ex)
       {
         shell.println("Error occured executing " + command.getClass().getSimpleName());
   
         return UNSUCCESSFUL; 
       }
      }
     }   
   return UNSUCCESSFUL;
 }

  /**
  * Executes with the current arguments.<br>
  * Should not manually invoke.
  */
  public void execute()
  {;
    int size = args.size();
    for (; i < size; i++)
    {
      String code = args.get(i);
      code = code.trim();
      if (code.startsWith("#"))
      {
        if (code.length() > 0)
        {
         if (!checkConditional(code,i))
          return;
        } 
      }
      else
      switch (ranSuccessfully(code))
      {
       case UNSUCCESSFUL:
        shell.println("Could not execute:" + code);
        throwCompletion(false);
        return;
       case INCOMPLETE:
        return;
      }
    }
   throwCompletion(true);
  }
 
 /**
 * The method that is called when the execution is over, alerting 
 * the ChainableListeners passed that it is done.
 * Should not manually invoke.
 */
 public void throwCompletion(boolean success)
 {
     final ChainEvent e = new ChainEvent(this, success);
     SwingUtilities.invokeLater(
       new Runnable()
       {
         public void run()
         {
            for (ChainableListener listener : listeners)
            { 
              listener.chainComplete(e);
            }
         }
       }
    );
 }

 /**
 * Clears the arguments between start, inclusively, and end, exclusively.
 * Should not manually invoke.
 */
 public void clearArgs(int start, int end)
 {
   for (; start < end; start++)
     args.set(start, "");
 }

 /**
 * Checks whether the macro script, starting at index i. The code argument is the line at index i.
 */
 public boolean checkConditional(String code, int i)
 {
  conditionalCheck = true;

   if (startsWithSymbol(IF_SYMBOL, code))
  {

      int ifCount = 1;
      elseIndex = -1;
      int size = args.size();
      for (end = i + 1; end < size; end++)
      {
       String token;
       args.set(end, (token = args.get(end).trim()));

       if (token.equals(END_IF_SYMBOL))
       {
          if (ifCount == 1)
          {
             switch(ranSuccessfully(token=code.substring(IF_SYMBOL.length() + 1, code.length())))
             {
               case SUCCESSFUL:
                if (elseIndex != - 1)
                 clearArgs(elseIndex + 1, end);
                break;
                case UNSUCCESSFUL:
                 clearArgs(i + 1, elseIndex == -1 ? end : elseIndex);
                 break;
               default:
               return false;
             }
            break;
          }
          else
           ifCount--;         
       }
       else if (token.equals(ELSE_SYMBOL))
       {
         if (ifCount == 1)
          if (elseIndex == -1)
           elseIndex = end;
          else
          {
           conditionalCheck = false;
           return true;
          }
       }
       else if (startsWithSymbol(IF_SYMBOL,token))
       {
         ifCount++;
       }
     }
  }
  conditionalCheck = false;
  return true;
 }

 /**
 * Returns if the line starts with the given symbol, ignoring whitespace.
 */
 public static boolean startsWithSymbol (String symbol, String text)
 {
   int symbolLength = symbol.length();
   return text.length() > symbolLength &&
              text.startsWith(symbol) &&
               Character.isWhitespace(text.charAt(symbolLength));
 }


  public void chainComplete(ChainEvent e)
  {
    if (e.wasSuccessful())
    {
      if (conditionalCheck)
      {
        if (elseIndex != - 1)
         clearArgs(elseIndex + 1, end);
        conditionalCheck = false;
      }
      i++;
      execute();

    }
    else
    {
       if (conditionalCheck)
       {
         clearArgs(i + 1, elseIndex == -1 ? end : elseIndex);
         conditionalCheck = false;
         i++;
         execute();
       }
       else
       {
        shell.println("Error occured executing " + command.getClass().getSimpleName());
        throwCompletion(false);
       }
     }
       
  }

}
